/*
 *  Pedometer - Android App
 *  Copyright (C) 2009 Levente Bagi
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.m2dl.niptus.pathtrack.step;

import java.util.ArrayList;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

import com.m2dl.niptus.pathtrack.db.DBAdapter;
import com.m2dl.niptus.pathtrack.utils.Constants;

/**
 * Detects steps and notifies all listeners (that implement StepListener).
 * 
 * @author Levente Bagi
 * @todo REFACTOR: SensorListener is deprecated
 */

public class StepDetector implements SensorEventListener {
	private final String TAG = Constants.TAG + "." + getClass().getSimpleName();

	private float mLimit = 10;
	private float mLastValues[] = new float[3 * 2];
	private float mScale[] = new float[2];
	private float mYOffset;

	private float mLastDirections[] = new float[3 * 2];
	private float mLastExtremes[][] = { new float[3 * 2], new float[3 * 2] };
	private float mLastDiff[] = new float[3 * 2];
	private int mLastMatch = -1;

	private ArrayList<StepListener> mStepListeners = new ArrayList<StepListener>();

	/** Database interaction **/
	private DBAdapter mDb;
	private Context mContext;
	private float x = 0, y = 0;
	
	/** Sensor data **/
	private float[] mGravity;
	private float[] mGeomagnetic;
	private float mAngle;

	public StepDetector(Context context) {
		mContext = context;

		// Clean database
		mDb = new DBAdapter(mContext);
		mDb.cleanDB();
		mDb.close();

		// TODO: remove this constant
		int h = 480;
		mYOffset = h * 0.5f;
		mScale[0] = -(h * 0.5f * (1.0f / (SensorManager.STANDARD_GRAVITY * 2)));
		mScale[1] = -(h * 0.5f * (1.0f / (SensorManager.MAGNETIC_FIELD_EARTH_MAX)));
	}

	public void addStepListener(StepListener sl) {
		mStepListeners.add(sl);
	}

	public void onSensorChanged(SensorEvent event) {
		synchronized (this) {
			if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
				// Save for orientation
				mGravity = event.values;
				processSensorData();
				
				float vSum = 0;
				for (int i = 0; i < 3; i++) {
					final float v = mYOffset + event.values[i] * mScale[1];
					vSum += v;
				}
				int k = 0;
				float v = vSum / 3;

				float direction = (v > mLastValues[k] ? 1 : (v < mLastValues[k] ? -1 : 0));
				if (direction == -mLastDirections[k]) {
					// Direction changed
					int extType = (direction > 0 ? 0 : 1);
					mLastExtremes[extType][k] = mLastValues[k];
					float diff = Math.abs(mLastExtremes[extType][k] - mLastExtremes[1 - extType][k]);

					if (diff > mLimit) {
						boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3);
						boolean isPreviousLargeEnough = mLastDiff[k] > (diff / 3);
						boolean isNotContra = (mLastMatch != 1 - extType);

						if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough && isNotContra) {
							Log.i(TAG, "step");
							
							// Insert step into Database
							x += Math.cos(mAngle) * Constants.STEP_SIZE;
							y += Math.sin(mAngle) * Constants.STEP_SIZE;
							
							mDb = new DBAdapter(mContext);
							mDb.insertObject(x, y);
							mDb.close();
							
							for (StepListener stepListener : mStepListeners) {
								stepListener.onStep();
							}
							mLastMatch = extType;
						} else {
							mLastMatch = -1;
						}
					}
					mLastDiff[k] = diff;
				}
				mLastDirections[k] = direction;
				mLastValues[k] = v;
			}

			if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
				mGeomagnetic = event.values;
				processSensorData();
			}
		}
	}
	
	public void processSensorData() {
		if (mGravity != null && mGeomagnetic != null) {
			float R[] = new float[9];
			float I[] = new float[9];
			boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
			if (success) {
				float orientation[] = new float[3];
				SensorManager.getOrientation(R, orientation);
				mAngle = -orientation[0]; // Azimuth
			}

			// Reset data
			mGravity = null;
			mGeomagnetic = null;
		}
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}
}